#include <ff_plugin.h>
#include <system_log.h>
#include <IUnityGraphicsVulkan.h>

namespace FFSdkUnity {

Plugin *Plugin::instance_ = nullptr;

void Plugin::Init(IUnityInterfaces &interfaces) noexcept
{
    FFLOGD("Plugin::Init start");
    if (!AcquireGraphics(interfaces)) {
        return;
    }

    instance_ = this;
    RegisterCallbacks();
    FFLOGD("Plugin::Init end");
}

void Plugin::Destroy() noexcept
{
    FFLOGD("Plugin::Destroy");
    UnregisterCallbacks();
    graphics_ = nullptr;
    instance_ = nullptr;
    FFLOGD("Plugin::Destroy end");
}

OpenGL::FFWrapperGL &Plugin::GetWrapperGL() noexcept
{
    return ffWrapperGL_;
}

Vulkan::FFWrapperVK &Plugin::GetWrapperVK() noexcept
{
    return ffWrapperVK_;
}

bool Plugin::AcquireGraphics(IUnityInterfaces &interfaces) noexcept
{
    interfaces_ = &interfaces;
    graphics_ = interfaces.Get<IUnityGraphics>();

    if (graphics_) {
        FFLOGD("Sdk::Unity::Plugin::AcquireGraphics - acquire graphics interface success!");
        return true;
    }

    FFLOGE("Sdk::Unity::Plugin::AcquireGraphics - Can't acquire graphics interface!");
    return false;
}

void Plugin::RegisterCallbacks() noexcept
{
    FFLOGI("Plugin::RegisterCallbacks start");

#define FF_REGISTER(event, callback) callbacks_[static_cast<size_t>((event))] = (callback)

    FF_REGISTER(UnityGfxDeviceEventType::kUnityGfxDeviceEventInitialize, &Plugin::OnInitialize);
    FF_REGISTER(UnityGfxDeviceEventType::kUnityGfxDeviceEventShutdown, &Plugin::OnShutdown);
    FF_REGISTER(UnityGfxDeviceEventType::kUnityGfxDeviceEventBeforeReset, &Plugin::OnBeforeReset);
    FF_REGISTER(UnityGfxDeviceEventType::kUnityGfxDeviceEventAfterReset, &Plugin::OnAfterReset);

#undef FF_REGISTER

    graphics_->RegisterDeviceEventCallback(&Plugin::OnDeviceEvent);

    Plugin::OnDeviceEvent(UnityGfxDeviceEventType::kUnityGfxDeviceEventInitialize);

    FFLOGI("Plugin::RegisterCallbacks end");
}

void Plugin::UnregisterCallbacks() noexcept
{
    graphics_->UnregisterDeviceEventCallback(&Plugin::OnDeviceEvent);
    callbacks_.fill(nullptr);
}

void Plugin::OnAfterReset() noexcept
{
    FFLOGD("Sdk::Unity::Plugin::OnAfterReset");
}

void Plugin::OnBeforeReset() noexcept
{
    FFLOGD("Sdk::Unity::Plugin::OnBeforeReset");
}

void Plugin::OnInitialize() noexcept
{
    FFLOGI("Plugin::OnInitialize start");

    auto renderer = graphics_->GetRenderer();
    if (renderer == UnityGfxRenderer::kUnityGfxRendererVulkan) {
        auto unity_vk = interfaces_->Get<IUnityGraphicsVulkan>();
        FFLOGI("Plugin::OnInitialize - Renderer Vulkan");
        if (!ffWrapperVK_.Init(unity_vk)) {
            FFLOGE("Plugin::OnInitialize - Can't init FrameFlow VK wrapper");
        }
        FFLOGI("Plugin::OnInitialize Vulkan success");
    } else if (renderer == UnityGfxRenderer::kUnityGfxRendererOpenGLES30) {
        FFLOGI("Sdk::Unity::Plugin::OnInitialize - Renderer GLES30");
        if (!ffWrapperGL_.Init()) {
            FFLOGE("Sdk::Unity::Plugin::OnInitialize - Can't init FrameFlow GL wrapper");
        }
        FFLOGI("Plugin::OnInitialize OpenGL success");
    } else {
        FFLOGE("Sdk::Unity::Plugin::OnInitialize - Renderer %d not supported", static_cast<int>(renderer));
    }
}

void Plugin::OnShutdown() noexcept
{
    auto renderer = graphics_->GetRenderer();
    if (renderer == UnityGfxRenderer::kUnityGfxRendererVulkan) {
        if (!ffWrapperVK_.Destroy()) {
            FFLOGE("Sdk::Unity::Plugin::OnShutdown - Can't destroy frameflow VK wrapper");
        }
    }
}

void UNITY_INTERFACE_API Plugin::OnDeviceEvent(UnityGfxDeviceEventType eventType)
{
    if (!instance_) {
        return;
    }

    auto const selector = static_cast<size_t>(eventType);
    auto const &callbacks = instance_->callbacks_;

    if (eventType >= callbacks.size()) {
        FFLOGE("Sdk::Unity::Plugin::OnDeviceEvent - Unexpected device event type: %zu", selector);
        return;
    }

    Callback const callback = callbacks[selector];
    (instance_->*callback)();
}

} // namespace FFSdkUnity
